package com.ken.zeratul.dlock.aspect;


import cn.hutool.core.util.ObjectUtil;
import com.ken.zeratul.commons.spring.SpelUtils;
import com.ken.zeratul.dlock.annotation.DLock;
import com.ken.zeratul.dlock.annotation.DLockType;
import com.ken.zeratul.dlock.utils.DLockUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * 分布式锁的切面
 */
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE + 1000)
@Slf4j
public class DLockAspect {

    private static final String DLOCK_PREFIX = "dlock:";

    /**
     * 分布式锁处理增强，增强添加了@DLock增强的业务方法
     * @return
     */
    @Around("@annotation(com.ken.zeratul.dlock.annotation.DLock)")
    public Object lockHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取当前方法签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();

        //获取方法上的注解
        DLock dLock = method.getAnnotation(DLock.class);
        if (ObjectUtil.isNull(dLock)) return joinPoint.proceed();

        //判断是否需要跳过分布式锁
        String nonLockEl = dLock.nonLock();
        if (!ObjectUtils.isEmpty(nonLockEl)) {
            //如果表达式不为空，则判断表达式
            Boolean skipLock = SpelUtils.parseExpression(nonLockEl, joinPoint.getTarget(), method, joinPoint.getArgs(), Boolean.class, false);
            if (skipLock) {
                //确定跳过分布式锁的过程，直接调用目标方法
                return joinPoint.proceed();
            }
        }

        //获取锁的名称，根据Spel表达式
        String key = SpelUtils.parseExpression(dLock.key(), joinPoint.getTarget(), method, joinPoint.getArgs(), String.class, null);
        if (StringUtils.isEmpty(key)) {
            //未解析出分布式锁的名称，直接调用目标方法
            return joinPoint.proceed();
        }
        //拼接前缀
        String lockName = DLOCK_PREFIX + (StringUtils.isEmpty(dLock.prefix()) ? "" : (dLock.prefix() + ":")) + key;

        //获取分布式锁的类型
        DLockType dLockType = dLock.lockType();

        //获取是否阻塞
        boolean isBlocking = dLock.blocking();

        try {
            if (isBlocking) {
                //阻塞锁
                DLockUtils.lock(dLockType, lockName);
            } else {
                //非阻塞锁
                boolean flag = DLockUtils.tryLock(dLockType, lockName, dLock.timeout());
                if (!flag) {
                    //没有获得分布式锁，则调用降级方法
                    return execFallBack(dLock.fallback(), joinPoint.getTarget(), joinPoint.getArgs());
                }
            }

            //执行目标业务
            return joinPoint.proceed();
        } finally {
            //解除分布式锁
            DLockUtils.unlock();
        }
    }

    /**
     * 调用降级方法
     * @return
     */
    private Object execFallBack(String methodName, Object targetObj, Object[] args){
        System.out.println("目标对象：" + targetObj.getClass().getName());
        //通过反射调用fallback方法
        try {
            //获取参数类型
            Class[] argCls = null;
            if (ObjectUtil.isNotEmpty(args)) {
                argCls = Arrays.stream(args)
                        .map(Object::getClass)
                        .collect(Collectors.toList())
                        .toArray(new Class[0]);
            }

            //执行目标方法
            Object result = targetObj.getClass()
                    .getMethod(methodName, argCls)
                    .invoke(targetObj, args);
            log.info("【分布式锁】调用降级方法成功！返回结果：{}", result);
            return result;
        } catch (Exception e) {
            log.error("【分布式锁】调用降级方法失败！方法名称：{}", methodName, e);
        }
        return null;
    }
}
